#define CMAID_DEBUG

#ifndef CMAID_GTEST_H
#define CMAID_GTEST_H

#include <Cmaid.h>
#include <gtest/gtest.h>
#include <stddef.h>

typedef int (*Cmaid_Gtest_Data_Compare)(const void *data1, const void *data2);
typedef std::string (*Cmaid_Gtest_Data_To_String)(const void *data);
typedef void *(*Cmaid_Gtest_Data_Get)(void *data, size_t i);

/* Yes, I'm a C programmer! */
typedef struct Cmaid_Gtest_Data Cmaid_Gtest_Data;
struct Cmaid_Gtest_Data
{
        void *data;
        size_t count;
	Cmaid_Gtest_Data_Compare compare;
	Cmaid_Gtest_Data_Compare compare_alt;
	Cmaid_Gtest_Data_To_String to_string;
	Cmaid_Gtest_Data_Get data_get;
};

typedef Cmaid_Container *(*Cmaid_Gtest_Constructor_Single)(const void *param);
#define CMAID_GTEST_CONSTRUCTOR_SINGLE(cb) (Cmaid_Gtest_Constructor_Single)(cb)

typedef struct Cmaid_Gtest_Constructor_Param Cmaid_Gtest_Constructor_Param;

struct Cmaid_Gtest_Constructor_Param
{
        const void *param; /* passed to the constructor */
        unsigned int pointer_type; /* is the value type a pointer? */
};

extern Cmaid_Gtest_Constructor_Param *cmaid_gtest_string_param_first;
extern Cmaid_Gtest_Constructor_Param *cmaid_gtest_string_param_last;
extern Cmaid_Gtest_Constructor_Param *cmaid_gtest_pointer_param_first;
extern Cmaid_Gtest_Constructor_Param *cmaid_gtest_pointer_param_last;
extern Cmaid_Gtest_Constructor_Param cmaid_gtest_integer_param;

#if GTEST_HAS_COMBINE
/* this is an abstract class for all cmaid containers that are dealing with
 * only one data, i.e everything except the map */
class Cmaid_Gtest_Container_Single
	: public ::testing::TestWithParam
          < ::std::tr1::tuple<
	  	Cmaid_Gtest_Constructor_Single,
		Cmaid_Gtest_Constructor_Param, /* parameter passed to
                                                         the constructor */
                Cmaid_Gtest_Data
            > /* the data to insert into the container */
          >
{
        protected:

        Cmaid_Container *container;
        Cmaid_Gtest_Data data;
        bool pointer_type;

        virtual void SetUp()
        {
                Cmaid_Gtest_Constructor_Param param;
                Cmaid_Gtest_Constructor_Single constructor;

                constructor = ::std::tr1::get<0>(GetParam());
                param = ::std::tr1::get<1>(GetParam());
                data = ::std::tr1::get<2>(GetParam());

                pointer_type = param.pointer_type;
                /* create the list */
                container = constructor(param.param);
                ASSERT_TRUE(NULL != container);
        }
        virtual void TearDown()
        {
                if (container)
                        cmaid_container_destroy(container);
                container = NULL;
        }
        bool isPointerType()
        {
                return pointer_type;
        }
        size_t dataCount()
        {
                return data.count;
        }
        void *dataAt(size_t i)
        {
                return data.data_get(data.data, i);
        }
        bool dataEquals(const void *data1, const void *data2)
        {
                return !data.compare(data1, data2);
        }
        int dataCompare(const void *data1, const void *data2)
        {
                return data.compare(data1, data2);
        }
        int dataCompareAlt(const void *data1, const void *data2)
        {
                return data.compare_alt(data1, data2);
        }
        Cmaid_Value_Compare_Cb dataCompareAltGet()
        {
                return data.compare_alt;
        }
        std::string dataToString(const void *d)
        {
                return data.to_string(d);
        }
        virtual void fill() = 0;
};

#endif /* GTEST_HAS_COMBINE */

extern const Cmaid_Gtest_Data cmaid_gtest_names;
extern const Cmaid_Gtest_Data cmaid_gtest_first_names;
extern const Cmaid_Gtest_Data cmaid_gtest_numbers;
extern const Cmaid_Gtest_Data *cmaid_gtest_pointers_first;
extern const Cmaid_Gtest_Data *cmaid_gtest_pointers_last;
extern const Cmaid_Value cmaid_gtest_dup_string;
extern const Cmaid_Value cmaid_gtest_inline_integer;

#define CMAID_GTEST_CASE_INST(type, dataname, ctor, ...) \
	INSTANTIATE_TEST_CASE_P(dataname, \
			Cmaid_##type##_Gtest, \
			Combine( \
				Values(CMAID_GTEST_CONSTRUCTOR_SINGLE(&ctor)), \
			##__VA_ARGS__ \
		))
#define CMAID_GTEST_CASE_INST_PAIR(type, dataname, ctor, ...) \
	INSTANTIATE_TEST_CASE_P(dataname, \
			Cmaid_##type##_Gtest, \
			Combine( \
				Values(CMAID_GTEST_CONSTRUCTOR_PAIR(&ctor)), \
			##__VA_ARGS__ \
		))

#define CMAID_GTEST_STRING_PARAMS ValuesIn(cmaid_gtest_string_param_first, \
		cmaid_gtest_string_param_last)
#define CMAID_GTEST_STRING_VALUES Values(cmaid_gtest_names, \
		cmaid_gtest_first_names)

#define CMAID_GTEST_INTEGER_PARAMS Values(cmaid_gtest_integer_param)
#define CMAID_GTEST_INTEGER_VALUES Values(cmaid_gtest_numbers)

#define CMAID_GTEST_POINTER_PARAMS ValuesIn(cmaid_gtest_pointer_param_first, \
		cmaid_gtest_pointer_param_last)
#define CMAID_GTEST_POINTER_VALUES ValuesIn(cmaid_gtest_pointers_first, \
		cmaid_gtest_pointers_last)

#define ASSERT_DATA_EQ(d1, d2) ASSERT_TRUE(dataEquals((d1), (d2))) << \
		dataToString((d1)) << " != " << dataToString((d2))

#define ASSERT_DATA_NE(d1, d2) ASSERT_FALSE(dataEquals(d1, d2)) << \
		dataToString(d1) << " == " << dataToString(d2)

#define ASSERT_ITER_IS(iter, index, data) \
	do { \
		void *__data = data; \
		\
		ASSERT_EQ(index, cmaid_iter_index_get(iter)); \
		if (__data) \
			ASSERT_DATA_EQ(__data, cmaid_iter_current(iter)); \
		else \
			ASSERT_TRUE(NULL == cmaid_iter_current(iter)); \
	} while (0)


#include "cmaid_list_gtest.h"
#include "cmaid_set_gtest.h"
#include "cmaid_cache_gtest.h"
#include "cmaid_map_gtest.h"

#endif /* CMAID_GTEST_H */
